package com.interviews.atomic;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.LongAdder;

/**
 * Adder 加法器
 * 两种加法器：LongAdder和DoubleAdder
 *
 * Accumulator 积累器
 * LongAccumulator和 DoubleAccumulator
 *
 * LongAdder 带来的改进和原理
 *
 * 为什么高并发下 LongAdder 比 AtomicLong 效率更高。
 *
 * 因为 LongAdder 引入了分段累加的概念，内部一共有两个参数参与计数：第一个叫作 base，它是一个变量，第二个是 Cell[]，是一个数组。
 *
 * 其中的 base 是用在竞争不激烈的情况下的，可以直接把累加结果改到 base 变量上。
 *
 * 那么，当竞争激烈的时候，就要用到我们的Cell[]数组了。一旦竞争激烈，各个线程会分散累加到自己所对应的那个 Cell[] 数组的某一个对象中，而不会大家共用同一个。
 *
 * 这样一来，LongAdder 会把不同线程对应到不同的 Cell 上进行修改，降低了冲突的概率，这是一种分段的理念，提高了并发性，这就和 Java 7 的 ConcurrentHashMap 的 16 个 Segment 的思想类似。
 *
 * 竞争激烈的时候，LongAdder 会通过计算出每个线程的 hash 值来给线程分配到不同的 Cell 上去，每个 Cell 相当于是一个独立的计数器，
 * 这样一来就不会和其他的计数器干扰，Cell 之间并不存在竞争关系，所以在自加的过程中，就大大减少了刚才的flush和refresh，以及降低了
 * 冲突的概率，这就是为什么LongAdder 的吞吐量比 AtomicLong 大的原因，本质是空间换时间，因为它有多个计数器同时在工作，所以占用
 * 的内存也要相对更大一些。
 *
 * 那么 LongAdder 最终是如何实现多线程计数的呢？
 * 答案就在最后一步的求和 sum 方法，执行 LongAdder.sum() 的时候，会把各个线程里的 Cell 累计求和，并加上 base，形成最终的总和。
 *
 * @author qian
 * @version 1.0
 * @date 2022/3/2 11:11
 */
public class AdderDemo {


    public static void main(String[] args) throws InterruptedException {
        LongAdder counter = new LongAdder();
        ThreadPoolExecutor executor = new ThreadPoolExecutor(16, 16,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());

        for (int i = 0; i < 100; i++) {
            executor.submit(new Task(counter));
        }
        Thread.sleep(2000);
        System.out.println(counter.sum());
    }

    static class Task implements Runnable {

        private final LongAdder counter;

        public Task(LongAdder counter) {
            this.counter = counter;
        }


        @Override
        public void run() {
            counter.increment();
        }
    }
}
